package com.hzlj.position.locate.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.fz.common.base.core.FormatMinuteUtil;
import com.fz.common.base.core.JsonUtils;
import com.fz.common.base.core.StringUtils;
import com.fz.common.base.core.date.DateUtils;
import com.fz.common.base.exception.PubException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hzlj.common.message.common.dto.message.MessageNoticeResultDTO;
import com.hzlj.common.message.common.dto.message.MessageNoticeUserDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.JzJbxxDeviceBindSimpleQueryParamDTO;
import com.hzlj.position.config.common.dto.noticeConfig.InspectionNoticeExtDTO;
import com.hzlj.position.config.common.enums.ReportType;
import com.hzlj.position.config.config.PositionConfig;
import com.hzlj.position.config.service.JzJbxxDeviceBindService;
import com.hzlj.position.config.service.NoticeConfigService;
import com.hzlj.position.locate.common.common.PositionSimpleDTO;
import com.hzlj.position.locate.common.dto.messageToday.MessageItemDTO;
import com.hzlj.position.locate.common.dto.messageToday.MessageTodayAddDTO;
import com.hzlj.position.locate.common.dto.reportCommon.ReportNoticeResultDTO;
import com.hzlj.position.locate.common.dto.reportInspection.ReportInspectionArtificialNoticeDTO;
import com.hzlj.position.locate.common.dto.reportInspection.ReportInspectionCorrectSendNoticeDTO;
import com.hzlj.position.locate.common.dto.reportInspection.ReportInspectionExtDTO;
import com.hzlj.position.locate.common.dto.reportInspection.ReportInspectionNoticeDTO;
import com.hzlj.position.locate.common.dto.reportInspectionAutoSetting.ReportInspectionAutoSettingQueryResultDTO;
import com.hzlj.position.locate.common.dto.reportSync.ReportSyncUpsertDTO;
import com.hzlj.position.locate.common.enums.ReportStatus;
import com.hzlj.position.locate.common.model.ReportInspection;
import com.hzlj.position.locate.converter.ReportInspectionConverter;
import com.hzlj.position.locate.dao.ReportInspectionArtificialDao;
import com.hzlj.position.locate.dao.ReportInspectionDao;
import com.hzlj.position.locate.service.*;
import com.hzlj.sqjz.config.common.enums.HandleStatus;
import com.hzlj.sqjz.jz.cache.JzJbxxCache;
import com.hzlj.sqjz.jz.common.dto.common.JzCommonQueryResultDTO;
import com.hzlj.sqjz.jz.common.dto.common.JzJbxxCacheDTO;
import com.hzlj.sqjz.jz.service.JzJbxxService;
import com.hzlj.sqjz.login.common.dto.userAuth.UserAuthWithRyxxQueryResultDTO;
import com.hzlj.sqjz.login.service.UserAuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.hzlj.position.config.config.IdConfig.POSITION_ID_GEN;

/**
 * 通信联络核查-核查(ReportInspection)表服务接口
 *
 * @author lifh
 * @date 2023-07-21 14:20:19
 */
@Slf4j
@Service
public class ReportInspectionSyncServiceImpl implements ReportInspectionSyncService, MessageTemplateBussService {
    @Resource
    private ReportInspectionDao reportInspectionDao;
    @Resource
    private ReportSyncService reportSyncService;
    @Resource
    private ReportInspectionAutoSettingService reportInspectionAutoSettingService;
    @Resource
    private ReportInspectionService reportInspectionService;
    @Resource
    private ReportInspectionCorrectService reportInspectionCorrectService;
    @Resource
    private NoticeConfigService noticeConfigService;
    @Resource
    private JzJbxxDeviceBindService jzJbxxDeviceBindService;
    @Resource
    private JzJbxxService jzJbxxService;
    @Resource
    private ReportInspectionArtificialDao reportInspectionArtificialDao;
    @Resource
    private PositionConfig positionConfig;
    @Resource
    private MessageTodayService messageTodayService;
    @Resource
    private JzJbxxCache jzJbxxCache;
    @Resource
    private UserAuthService userAuthService;

    /**
     * 人工点验
     */
    @Transactional(rollbackFor = Exception.class)
    public ReportNoticeResultDTO syncReportInspectionArtificial(ReportInspectionArtificialNoticeDTO dto) {
        List<String> jzIds = dto.getJzIds();
        if (ObjectUtil.isEmpty(jzIds)) {
            JzJbxxDeviceBindSimpleQueryParamDTO params = jzJbxxDeviceBindService.getQueryParam4App()
                    .setMkh(!positionConfig.getInspection().getExcludeMkh())
                    .setQzcs(!positionConfig.getInspection().getExcludeQzcs());
            params.setJgCode(dto.getJgCode());
            jzIds = jzJbxxDeviceBindService.listJzIds(params);
        }
        if (ObjectUtil.isEmpty(jzIds)) {
            log.warn("人工核验：没有需要发送的数据");
            return new ReportNoticeResultDTO("没有需要发送的数据");
        }
        //校验是否有绑定记录
        for (String jzId : dto.getJzIds()) {
            if (!jzJbxxDeviceBindService.existsJzJbxxDeviceBind4App(jzId)) {
                throw new PubException("没有App绑定信息,不能发起核验");
            }
        }
        //生成记录
        Date now = new Date();
        List<ReportInspection> addReports = dto.getJzIds().stream().map(jzId -> {
            ReportInspection inspection = this.defaultConvert(jzId, null, now, ReportType.ARTIFICIAL);
            inspection.setCreatorUid(dto.getCreatorUid());
            inspection.setCreatorName(dto.getCreatorName());
            inspection.setCreatorJgCode(dto.getCreatorJgCode());
            inspection.setCreatorJgMc(dto.getCreatorJgMc());
            return inspection;
        }).collect(Collectors.toList());

        if (ObjectUtil.isNotEmpty(addReports)) {
            this.reportInspectionDao.saveBatch(addReports);
        }
        //同步到最近记录中
        reportInspectionArtificialDao.upsert(addReports, dto.getCreatorJgCode(), dto.getCreatorJgMc());
        //发起通知(延迟通知)
        this.syncNotice(addReports, dto.getCreatorUid());
        //发送通知(立即发送)
        return messageTodayService.send("report-inspection", addReports.stream().map(e -> {
                    JzCommonQueryResultDTO jzry = jzJbxxService.getJzCommonById(e.getJzId());
                    return new MessageItemDTO()
                            .setServiceId(e.getId())
                            .setBelongDay(e.getBelongDay())
                            .setUserId(jzry.getJzId())
                            .setUserName(jzry.getXm())
                            .setParams(this.convertMessageParams(e, e.getSpotCheckTime()));
                }).collect(Collectors.toList())
        );
    }

    /**
     * 自动点验
     */
    @Transactional(rollbackFor = Exception.class)
    public Integer syncReportInspectionAuto() {
        Date now = new Date();
        Date belongDay = DateUtil.beginOfDay(now);
        ReportType reportType = ReportType.AUTO;
        Map<String, ReportSyncUpsertDTO> syncLatestUpdateDayCache = Maps.newHashMap();
        //随机抽取的矫正人员
        List<Pair<String, ReportInspectionAutoSettingQueryResultDTO>> randomJzryBySetting = reportInspectionAutoSettingService.randomJzryBySetting(now);
        List<ReportInspection> allReports = randomJzryBySetting.stream().peek(e -> {
                    String jgCode = e.getValue().getJgCode();
                    syncLatestUpdateDayCache.putIfAbsent(jgCode, new ReportSyncUpsertDTO(jgCode, reportType.getCode(), belongDay));
                }).map(e -> autoConverter(e.getKey(), now, e.getValue()))
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        //保存核验数据
        if (ObjectUtil.isNotEmpty(allReports)) {
            List<List<ReportInspection>> allAddsSplit = CollUtil.split(allReports, 500);
            for (List<ReportInspection> adds : allAddsSplit) {
                reportInspectionDao.saveBatch(adds);
            }
        }

        //发起通知(延迟通知)
        List<MessageTodayAddDTO> jzries = allReports.stream().flatMap(e -> {
            List<MessageTodayAddDTO> messages = Lists.newArrayList();
            //首次发送
            MessageTodayAddDTO message = convert(e, e.getSpotCheckTime())
                    .setMessageTemplate("report-inspection")
                    .setUser(MessageNoticeUserDTO.of(false, e.getJzId()));
            messages.add(message);
            for (int i = 1; i <= e.getMaxRetryTimes(); i++) {
                messages.add(
                        BeanUtil.copyProperties(message, MessageTodayAddDTO.class)
                                .setNoticeTime(DateUtil.offset(message.getNoticeTime(), DateField.MINUTE, i * 5))
                );
            }
            return messages.stream();
        }).collect(Collectors.toList());
        messageTodayService.sendAsync(jzries);

        //发起通知(延迟通知)
        Map<String, List<ReportInspection>> allReportsByGzryId = allReports.stream().collect(Collectors.groupingBy(ReportInspection::getCreatorUid));
        allReportsByGzryId.forEach((gzryId, inspections) -> {
            this.syncNotice(inspections, gzryId);
        });

        //更新时间
        reportSyncService.upsert(syncLatestUpdateDayCache.values());
        return allReports.size();
    }


    /**
     * 发起纠偏通知
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MessageNoticeResultDTO syncReportInspectionCorrect(ReportInspectionCorrectSendNoticeDTO dto) {
        //没有app信息
        if (!jzJbxxDeviceBindService.existsJzJbxxDeviceBind4App(dto.getJzId())) {
            throw new PubException("没有App绑定信息,不能发起纠偏");
        }
        Date now = new Date();
        PositionSimpleDTO position = dto.getOriginalPosition();
        ReportInspection inspection = defaultConvert(dto.getJzId(), null, now, ReportType.CORRECT);
        inspection.setExt(
                new ReportInspectionExtDTO(position.getId(), position.getPositionTime())
        );
        //保存
        this.reportInspectionDao.save(inspection);

        //保存纠偏发起记录
        MessageNoticeResultDTO notice = reportInspectionCorrectService.send(position.getId(),
                position.getPositionTime(),
                BeanUtil.copyProperties(inspection, ReportInspectionNoticeDTO.class));

        //更新发送状态
        this.updateNotice(inspection, notice);
        return notice;
    }


    /**
     * 更新发送状态
     */
    private void updateNotice(ReportInspection inspection, MessageNoticeResultDTO notice) {
        if (ObjectUtil.hasEmpty(inspection, notice)) {
            log.warn("核验数据或者发送结果存在空记录:inspections={},notices={}", inspection, notice);
            return;
        }
        this.reportInspectionDao.updateNotice(
                inspection.getId(),
                inspection.getBelongDay(),
                !notice.isFail(),
                notice.isFail() ? notice.getStatusMsg() : null
        );
    }

    /**
     * 发起位置确认通知(运营商错误返回代码，需要发起app定位)
     */
    @Override
    public void syncReportInspectionPositionCheck(String jzId, String jgCode, ReportInspectionExtDTO ext) {
        Date now = new Date();
        //保存核验记录
        ReportInspection inspection = this.defaultConvert(jzId, jgCode, now, ReportType.POSITION_CHECK);
        inspection.setExt(ext);
        this.reportInspectionDao.save(inspection);
        //TODO 发送通知
    }

    /**
     * 校验是否需要发送通知
     */
    @Override
    public boolean checkNeedSend(String messageTemplate,
                                 String serviceId,
                                 Date belongDay,
                                 Map<String, Object> messageParams,
                                 Map<String, Object> checkParams) {
        //其他的只要校验没有签到即可
        ReportInspection reportInspection = this.reportInspectionDao.getReportInspection(serviceId, belongDay);
        if (reportInspection == null) {
            return false;
        }
        //已经发送成功的数据不需要继续发送
        if (messageTemplate.equals("report-inspection")) {
            if (Boolean.TRUE.equals(reportInspection.getNotice())) {
                return false;
            }
        }
        //处理handle状态
        if (messageTemplate.equals("report-inspection-no-report-handle")) {
            if (reportInspection.getReportTime() == null) {
                log.info("核验信息未上报，需要更新handleStatus状态:id={},belongDay={}",serviceId,belongDay);
                this.reportInspectionDao.updateHandleStatus(serviceId, belongDay, HandleStatus.WAIT);
            }
            return false;
        }
        //没有上报
        return reportInspection.getReportTime() == null;
    }

    @Override
    public void updateNoticeResult(String messageTemplate,
                                   String serviceId,
                                   Date belongDay,
                                   Map<String, Object> checkParams,
                                   MessageNoticeResultDTO notice) {
        //点验通知
        if (messageTemplate.equals("report-inspection")) {
            ReportInspectionNoticeDTO inspection = this.reportInspectionDao.getReportInspection4Notice(serviceId, belongDay);
            if (inspection == null) {
                return;
            }
            Date now = new Date();
            ReportInspection update;
            //处理通知结果
            if (!notice.isFail()) {
                update = ReportInspectionConverter.toSuccess(inspection, now);
            } else {
                update = ReportInspectionConverter.toFail(inspection, notice, now);
            }
            this.reportInspectionService.updateByInspections(Lists.newArrayList(update));
        }
    }


    /**
     * 转换成report对象
     */
    private ReportInspection autoConverter(String jzId,
                                           Date now,
                                           ReportInspectionAutoSettingQueryResultDTO setting) {
        //时间转换
        Date startTime = DateUtils.parseFixedHHmm(now, setting.getStartTime());
        //如果开始时间小于当前时间，则取当前时间
        startTime = startTime.compareTo(now) < 0 ? now : startTime;
        Date endTime = DateUtils.parseFixedHHmm(now, StringUtils.min(setting.getEndTime(), "23:59"));
        if (endTime.compareTo(startTime) <= 0) {
            return null;
        }
        ReportInspection report = defaultConvert(jzId, setting.getJgCode(), now, ReportType.AUTO);
        report.setReportStatus(ReportStatus._WAIT.getStatus());
        report.setMaxRetryTimes(setting.getRetryTimes());
        report.setSpotCheckTime(
                RandomUtil.randomDate(startTime, DateField.MINUTE, 0, (int) DateUtil.between(startTime, endTime, DateUnit.MINUTE))
        );
        report.setExt(
                new ReportInspectionExtDTO(setting.getId(), setting.getCreatorJgCode(), setting.getCreatorJgMc())
        );
        report.setCreatorUid(setting.getCreatorUid());
        report.setCreatorName(setting.getCreatorName());
        report.setCreatorJgCode(setting.getCreatorJgCode());
        report.setCreatorJgMc(setting.getCreatorJgMc());
        return report;
    }

    public ReportInspection defaultConvert(String jzId,
                                           String jgCode,
                                           Date now,
                                           ReportType reportType) {
        ReportInspection report = new ReportInspection();
        report.setId(POSITION_ID_GEN.nextIdStr());
        report.setJzId(jzId);
        report.setBelongDay(DateUtil.beginOfDay(now));
        report.setReportType(reportType.getCode());
        report.setReportStatus(ReportStatus._NEVER.getStatus());//默认未签到
        report.setHandleStatus(HandleStatus.NONE.getStatus());
        report.setSpotCheckTime(now);
        report.setMaxRetryTimes(0);
        report.setValid(true);
        report.setCreateTime(now);
        report.setUpdateTime(now);
        //获取jgCode
        if (ObjectUtil.isEmpty(jgCode)) {
            JzCommonQueryResultDTO jzJbxx = jzJbxxService.getJzCommonById(jzId);
            jgCode = jzJbxx.getJgCode();
        }
        //设置通知时间
        int maxDelayReportMinutes;
        switch (reportType) {
            case AUTO:
                maxDelayReportMinutes = noticeConfigService.reportAutoDelayMinutes(jgCode);
                break;
            case ARTIFICIAL:
                maxDelayReportMinutes = noticeConfigService.reportArtificialDelayMinutes(jgCode);
                break;
            case TRACK_CHECK:
                maxDelayReportMinutes = noticeConfigService.trackCheckDelayMinutes(jgCode);
                break;
            case POSITION_CHECK:
                maxDelayReportMinutes = noticeConfigService.positionCheckDelayMinutes(jgCode);
                break;
            case CORRECT:
                maxDelayReportMinutes = noticeConfigService.correctDelayMinutes(jgCode);
                break;
            default:
                throw new PubException("未设置通知时间参数");
        }
        report.setMaxDelayReportMinutes(maxDelayReportMinutes);
        return report;
    }


    /**
     * 发送通知
     */
    private void syncNotice(List<ReportInspection> inspections, String gzryId) {
        if (ObjectUtil.isEmpty(inspections)) {
            return;
        }
        //查询人员信息
        List<Pair<ReportInspection, JzCommonQueryResultDTO>> inspectionPairs = inspections.stream().map(e -> {
            JzCommonQueryResultDTO jzJbxx = jzJbxxService.getJzCommonById(e.getJzId());
            return new Pair<>(e, jzJbxx);
        }).collect(Collectors.toList());

        //提前通知矫正对象
        List<MessageTodayAddDTO> jzries = inspectionPairs.stream()
                .filter(filter(InspectionNoticeExtDTO::getNoticeAutoJzryBefore, InspectionNoticeExtDTO::getNoticeArtificialJzryBefore))
                .map(e -> {
                    ReportInspection inspection = e.getKey();
                    return convert(inspection, null)
                            .setMessageTemplate("report-inspection-before")
                            .setUser(MessageNoticeUserDTO.of(false, inspection.getJzId()));
                }).collect(Collectors.toList());
        messageTodayService.sendAsync(jzries);

        //未上报处理通知
        jzries = inspectionPairs.stream()
                .filter(filter(InspectionNoticeExtDTO::getHandleIfNoReport, InspectionNoticeExtDTO::getHandleIfNoReport))
                .map(e -> {
                    ReportInspection inspection = e.getKey();
                    DateTime noticeTime = DateUtil.offset(inspection.getSpotCheckTime(), DateField.MINUTE, inspection.getMaxDelayReportMinutes());
                    return convert(inspection, noticeTime)
                            .setMessageTemplate("report-inspection-no-report-handle")
                            .setUser(MessageNoticeUserDTO.of(false, inspection.getJzId()));
                }).collect(Collectors.toList());
        messageTodayService.sendAsync(jzries);


        //提前通知工作人员
        List<MessageTodayAddDTO> gzries = inspectionPairs.stream()
                .filter(filter(InspectionNoticeExtDTO::getNoticeAutoGzryBefore, InspectionNoticeExtDTO::getNoticeArtificialGzryBefore))
                .flatMap(e -> messagesOfGzry("report-inspection-before-gzry", null, gzryId, e))
                .collect(Collectors.toList());
        messageTodayService.sendAsync(gzries);

        //完成后通知工作人员
        gzries = inspectionPairs.stream()
                .filter(filter(InspectionNoticeExtDTO::getNoticeAutoGzryAfter, InspectionNoticeExtDTO::getNoticeArtificialGzryAfter))
                .flatMap(e -> {
                    DateTime noticeTime = DateUtil.offset(e.getKey().getSpotCheckTime(), DateField.MINUTE, e.getKey().getMaxDelayReportMinutes());
                    return messagesOfGzry("report-inspection-after-gzry", noticeTime, gzryId, e);
                })
                .collect(Collectors.toList());
        messageTodayService.sendAsync(gzries);
    }

    private MessageTodayAddDTO convert(ReportInspection inspection,
                                       Date noticeTime) {
        JzCommonQueryResultDTO jzJbxx = jzJbxxService.getJzCommonById(inspection.getJzId());
        Integer noticeJzryMinutes = noticeConfigService.inspectionNotices(jzJbxx.getJgCode()).getNoticeJzryMinutes();
        if (noticeTime == null) {
            DateTime endTime = DateUtil.offset(inspection.getSpotCheckTime(), DateField.MINUTE, inspection.getMaxDelayReportMinutes());
            noticeTime = DateUtil.offset(endTime, DateField.MINUTE, -noticeJzryMinutes);
        }

        return new MessageTodayAddDTO()
                .setBelongDay(inspection.getBelongDay())
                .setMessageParams(this.convertMessageParams(inspection, noticeTime))
                .setNoticeTime(noticeTime)
                .setServiceId(inspection.getId());
    }

    private String convertMessageParams(ReportInspection inspection,
                                        Date noticeTime) {
        JzJbxxCacheDTO jzJbxx = jzJbxxCache.getJzJbxxByJzId(inspection.getJzId());
        return JsonUtils.obj2json(MapUtil.<String, Object>builder()
                .put("jzryXm", jzJbxx.getXm())
                .put("jzrySjh", jzJbxx.getSjh())
                .put("reportType", inspection.getReportType().equals(ReportType.AUTO.getCode())?"zddy":"rgdy")
                .put("belongDay", DateUtils.formatShortDate(inspection.getBelongDay()))
                .put("reportTypeText", ReportType.of(inspection.getReportType()).getText())
                .put("spotCheckTime", DateUtil.formatDateTime(noticeTime))
                .put("spotCheckDay", DateUtil.formatDate(noticeTime))
                .put("maxDelayHour", FormatMinuteUtil.formatToHour(inspection.getMaxDelayReportMinutes()))
                .build()
        );
    }

    private Predicate<Pair<ReportInspection, JzCommonQueryResultDTO>> filter(Predicate<InspectionNoticeExtDTO> auto,
                                                                             Predicate<InspectionNoticeExtDTO> artificial) {
        return e -> {
            InspectionNoticeExtDTO ext = noticeConfigService.inspectionNotices(e.getValue().getJgCode());
            return ReportType.AUTO.getCode().equals(e.getKey().getReportType()) ? auto.test(ext) : artificial.test(ext);
        };
    }

    private Stream<MessageTodayAddDTO> messagesOfGzry(String messageTemplate,
                                                      Date noticeTime,
                                                      String gzryId,
                                                      Pair<ReportInspection, JzCommonQueryResultDTO> pair) {
        List<MessageTodayAddDTO> list = Lists.newArrayList();
        ReportInspection inspection = pair.getKey();
        JzCommonQueryResultDTO jzry = pair.getValue();
        //默认通知角色
        String noticeAuths = noticeConfigService.defaultMessageNoticeAuths(jzry.getJgCode());
        //创建者，需要判断是否重复
        if (gzryId != null) {
            boolean repeat = false;
            if (ObjectUtil.isNotEmpty(noticeAuths)) {
                UserAuthWithRyxxQueryResultDTO userAuth = userAuthService.getUserAuthWithRyxx(null, gzryId);
                if (userAuth != null && ObjectUtil.isNotEmpty(userAuth.getRoleAuths())) {
                    //有交集
                    repeat = ObjectUtil.isNotEmpty(
                            CollectionUtil.intersection(userAuth.getRoleAuths(), Arrays.stream(noticeAuths.split(",")).collect(Collectors.toList()))
                    );
                }
            }

            if (!repeat) {
                list.add(convert(inspection, noticeTime)
                        .setMessageTemplate(messageTemplate)
                        .setUser(MessageNoticeUserDTO.of(true, gzryId))
                );
            }
        }
        //配置的角色
        if (ObjectUtil.isNotEmpty(noticeAuths)) {
            list.add(convert(inspection, noticeTime)
                    .setMessageTemplate(messageTemplate)
                    .setUser(MessageNoticeUserDTO.of(true, jzry.getJgCode(), noticeAuths))
            );
        }
        return list.stream();
    }


}
